/*
 * Copyright 1999-2011 Alibaba Group.
 *  
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *  
 *      http://www.apache.org/licenses/LICENSE-2.0
 *  
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alibaba.dubbo.common.serialize.support.dubbo;

import java.io.IOException;
import java.io.OutputStream;

import com.alibaba.dubbo.common.serialize.DataOutput;

/**
 * Default data output impl.
 * Not thread-safe.
 * 
 * @author qian.lei
 */

public class GenericDataOutput implements DataOutput, GenericDataFlags
{
	private static final int CHAR_BUF_SIZE = 256;

	private final byte[] mBuffer, mTemp = new byte[9];

	private final char[] mCharBuf = new char[CHAR_BUF_SIZE];

	private final OutputStream mOutput;

	private final int mLimit;

	private int mPosition = 0;

	public GenericDataOutput(OutputStream out)
	{
		this(out, 1024);
	}

	public GenericDataOutput(OutputStream out, int buffSize)
	{
		mOutput = out;
		mLimit = buffSize;
		mBuffer = new byte[buffSize];
	}

	public void writeBool(boolean v) throws IOException
	{
		write0( v ? VARINT_1 : VARINT_0 );
	}

	public void writeByte(byte v) throws IOException
	{
		switch( v )
		{
			case 0: write0(VARINT_0); break; case 1: write0(VARINT_1); break; case 2: write0(VARINT_2); break; case 3: write0(VARINT_3); break;
			case 4: write0(VARINT_4); break; case 5: write0(VARINT_5); break; case 6: write0(VARINT_6); break; case 7: write0(VARINT_7); break;
			case 8: write0(VARINT_8); break; case 9: write0(VARINT_9); break; case 10: write0(VARINT_A); break; case 11: write0(VARINT_B); break;
			case 12: write0(VARINT_C); break; case 13: write0(VARINT_D); break; case 14: write0(VARINT_E); break; case 15: write0(VARINT_F); break;
			case 16: write0(VARINT_10); break; case 17: write0(VARINT_11); break; case 18: write0(VARINT_12); break; case 19: write0(VARINT_13); break;
			case 20: write0(VARINT_14); break; case 21: write0(VARINT_15); break; case 22: write0(VARINT_16); break; case 23: write0(VARINT_17); break;
			case 24: write0(VARINT_18); break; case 25: write0(VARINT_19); break; case 26: write0(VARINT_1A); break; case 27: write0(VARINT_1B); break;
			case 28: write0(VARINT_1C); break; case 29: write0(VARINT_1D); break; case 30: write0(VARINT_1E); break; case 31: write0(VARINT_1F); break;
			default:
				write0(VARINT8);
				write0(v);
		}
	}

	public void writeShort(short v) throws IOException
	{
		writeVarint32(v);
	}

	public void writeInt(int v) throws IOException
	{
		writeVarint32(v);
	}

	public void writeLong(long v) throws IOException
	{
		writeVarint64(v);
	}

	public void writeFloat(float v) throws IOException
	{
		writeVarint32(Float.floatToRawIntBits(v));
	}

	public void writeDouble(double v) throws IOException
	{
		writeVarint64(Double.doubleToRawLongBits(v));
	}

	public void writeUTF(String v) throws IOException
	{
		if( v == null )
		{
			write0(OBJECT_NULL);
		}
		else
		{
			int len = v.length();
			if( len == 0 )
			{
				write0(OBJECT_DUMMY);
			}
			else
			{
				write0(OBJECT_BYTES);
				writeUInt(len);

				int off = 0, limit = mLimit - 3, size;
				char[] buf = mCharBuf;
				do
				{
					size = Math.min(len-off, CHAR_BUF_SIZE);
					v.getChars(off, off+size, buf, 0);

					for(int i=0;i<size;i++)
					{
						char c = buf[i];
						if( mPosition > limit )
						{
							if( c < 0x80 )
							{
								write0((byte)c);
							}
							else if( c < 0x800 )
							{
								write0((byte)(0xC0 | ((c >> 6) & 0x1F)));
								write0((byte)(0x80 | (c & 0x3F)));
							}
							else
							{
								write0((byte)(0xE0 | ((c >> 12) & 0x0F)));
								write0((byte)(0x80 | ((c >> 6) & 0x3F)));
								write0((byte)(0x80 | (c & 0x3F)));
							}
						}
						else
						{
							if( c < 0x80 )
							{
								mBuffer[mPosition++] = (byte)c;
							}
							else if( c < 0x800 )
							{
								mBuffer[mPosition++] = (byte)(0xC0 | ((c >> 6) & 0x1F));
								mBuffer[mPosition++] = (byte)(0x80 | (c & 0x3F));
							}
							else
							{
								mBuffer[mPosition++] = (byte)(0xE0 | ((c >> 12) & 0x0F));
								mBuffer[mPosition++] = (byte)(0x80 | ((c >> 6) & 0x3F));
								mBuffer[mPosition++] = (byte)(0x80 | (c & 0x3F));
							}
						}
					}
					off += size;
				}
				while( off < len );
			}
		}
	}

	public void writeBytes(byte[] b) throws IOException
	{
		if( b == null )
			write0(OBJECT_NULL);
		else
			writeBytes(b, 0, b.length);
	}

	public void writeBytes(byte[] b, int off, int len) throws IOException
	{
		if( len == 0 )
		{
			write0(OBJECT_DUMMY);
		}
		else
		{
			write0(OBJECT_BYTES);
			writeUInt(len);
			write0(b, off, len);
		}
	}

	public void flushBuffer() throws IOException
	{
		if( mPosition > 0 )
		{
			mOutput.write(mBuffer, 0, mPosition);
			mPosition = 0;
		}
	}

	public void writeUInt(int v) throws IOException
	{
		byte tmp;
		while( true )
		{
			tmp = (byte)( v & 0x7f );
			if( ( v >>>= 7 ) == 0 )
			{
				write0( (byte)( tmp | 0x80 ) );
				return;
			}
			else
			{
				write0(tmp);
			}
		}
	}

	protected void write0(byte b) throws IOException
	{
		if( mPosition == mLimit )
			flushBuffer();

		mBuffer[mPosition++] = b;
	}

	protected void write0(byte[] b, int off, int len) throws IOException
	{
		int rem = mLimit - mPosition;
		if( rem > len )
		{
			System.arraycopy(b, off, mBuffer, mPosition, len);
			mPosition += len;
		}
		else
		{
			System.arraycopy(b, off, mBuffer, mPosition, rem);
			mPosition = mLimit;
			flushBuffer();

			off += rem;
			len -= rem;

			if( mLimit > len )
			{
				System.arraycopy(b, off, mBuffer, 0, len);
				mPosition = len;
			}
			else
			{
				mOutput.write(b, off, len);
			}
		}
	}

	private void writeVarint32(int v) throws IOException
	{
		switch( v )
		{
			case -15: write0(VARINT_NF); break; case -14: write0(VARINT_NE); break; case -13: write0(VARINT_ND); break;
			case -12: write0(VARINT_NC); break; case -11: write0(VARINT_NB); break; case -10: write0(VARINT_NA); break; case -9: write0(VARINT_N9); break;
			case -8: write0(VARINT_N8); break; case -7: write0(VARINT_N7); break; case -6: write0(VARINT_N6); break; case -5: write0(VARINT_N5); break;
			case -4: write0(VARINT_N4); break; case -3: write0(VARINT_N3); break; case -2: write0(VARINT_N2); break; case -1: write0(VARINT_N1); break;
			case 0: write0(VARINT_0); break; case 1: write0(VARINT_1); break; case 2: write0(VARINT_2); break; case 3: write0(VARINT_3); break;
			case 4: write0(VARINT_4); break; case 5: write0(VARINT_5); break; case 6: write0(VARINT_6); break; case 7: write0(VARINT_7); break;
			case 8: write0(VARINT_8); break; case 9: write0(VARINT_9); break; case 10: write0(VARINT_A); break; case 11: write0(VARINT_B); break;
			case 12: write0(VARINT_C); break; case 13: write0(VARINT_D); break; case 14: write0(VARINT_E); break; case 15: write0(VARINT_F); break;
			case 16: write0(VARINT_10); break; case 17: write0(VARINT_11); break; case 18: write0(VARINT_12); break; case 19: write0(VARINT_13); break;
			case 20: write0(VARINT_14); break; case 21: write0(VARINT_15); break; case 22: write0(VARINT_16); break; case 23: write0(VARINT_17); break;
			case 24: write0(VARINT_18); break; case 25: write0(VARINT_19); break; case 26: write0(VARINT_1A); break; case 27: write0(VARINT_1B); break;
			case 28: write0(VARINT_1C); break; case 29: write0(VARINT_1D); break; case 30: write0(VARINT_1E); break; case 31: write0(VARINT_1F); break;
			default:
				int t = v, ix = 0;
				byte[] b = mTemp;

				while( true )
				{
					b[++ix] = (byte)( v & 0xff );
					if( ( v >>>= 8 ) == 0 )
						break;
				}

				if( t > 0 )
				{
					// [ 0a e2 => 0a e2 00 ] [ 92 => 92 00 ]
					if( b[ix] < 0 )
						b[++ix] = 0;
				}
				else
				{
					// [ 01 ff ff ff => 01 ff ] [ e0 ff ff ff => e0 ]
					while( b[ix] == (byte)0xff && b[ix-1] < 0 )
						ix--;
				}

				b[0] = (byte)( VARINT + ix - 1 );
				write0(b, 0, ix+1);
		}
	}

	private void writeVarint64(long v) throws IOException
	{
		int i = (int)v;
		if( v == i )
		{
			writeVarint32(i);
		}
		else
		{
			long t = v;
			int ix = 0;
			byte[] b = mTemp;

			while( true )
			{
				b[++ix] = (byte)( v & 0xff );
				if( ( v >>>= 8 ) == 0 )
					break;
			}

			if( t > 0 )
			{
				// [ 0a e2 => 0a e2 00 ] [ 92 => 92 00 ]
				if( b[ix] < 0 )
					b[++ix] = 0;
			}
			else
			{
				// [ 01 ff ff ff => 01 ff ] [ e0 ff ff ff => e0 ]
				while( b[ix] == (byte)0xff && b[ix-1] < 0 )
					ix--;
			}

			b[0] = (byte)( VARINT + ix - 1 );
			write0(b, 0, ix+1);
		}
	}
}